package com.sea.base.ext.global

import androidx.annotation.MainThread
import androidx.annotation.WorkerThread
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout
import java.util.concurrent.TimeoutException
import kotlin.coroutines.CoroutineContext

/**
 * 如果本来就在io线程，则不会重新切换
 */
suspend inline fun <T> withContextIO(@WorkerThread crossinline block: suspend () -> T): T {
    return if (!isMainThread()) block() else withContext(Dispatchers.IO) { block() }
}

/**
 * 如果本来就在main则不会重新切换
 */
suspend inline fun <T> withContextMain(@MainThread crossinline block: suspend () -> T): T {
    return withContext(Dispatchers.MainImmediate) { block() }
}

/**
 * 启动一个新的io协程
 */
inline fun CoroutineScope.launchWithIo(
    addContext: CoroutineContext? = null,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    @WorkerThread crossinline block: suspend CoroutineScope.() -> Unit
): Job {
    val context = if (addContext == null) Dispatchers.IO else (Dispatchers.IO + addContext)
    return launch(context, start) { block.invoke(this) }
}

/**
 * 启动一个新的io异步
 */
inline fun <T> CoroutineScope.asyncWithIo(
    addContext: CoroutineContext? = null,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    @WorkerThread crossinline block: suspend CoroutineScope.() -> T
): Deferred<T> {
    val context = if (addContext == null) Dispatchers.IO else (Dispatchers.IO + addContext)
    return async(context, start) { block.invoke(this) }
}

/**
 * 由于[withTimeout]触发的是Cancel而不是异常，这里将Timeout改为普通异常重新抛出
 */
suspend inline fun <T> withTimeoutThrow(timeMillis: Long, noinline block: suspend CoroutineScope.() -> T): T {
    try {
        return withTimeout(timeMillis, block)
    } catch (e: TimeoutCancellationException) {
        "协程等待超时，请检查".toastAndLogDebug()
        throw TimeoutException("协程超时主动抛出：${e.message}")
    }
}
